/*  
  -- The Lime II Project -- 

  A tuplespaces-based middleware for coordinating agents and mobile hosts.
  Copyright (C) 2005.
  Gian Pietro Picco, Amy L. Murphy, Lorenzo Bellini.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  Lesser General Public License for more details.

  You should have received this copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 */

package lime2;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.logging.Level;
import java.util.logging.Logger;

import lights.interfaces.ITuple;
import lights.interfaces.ITupleSpace;
import lights.interfaces.TupleSpaceException;
import sagroup.util.Arrayss;

class OptimizedReactor {
	static Logger logger = Logger.getLogger("com.wombat.nose");// "lime2.Reactor");

	IRRMgr rrMgr;

	// the tuplespace layer
	private ITupleSpace ts;

	// keeps track whether the reactor is running now
	private boolean running = false;

	private List<ReactorOp> ops = new LinkedList<ReactorOp>();
	{
		ops.add(new ReactorOp(LimeConstants.BYE, null));
	}

	private boolean[] deleteds;

	private ITuple[] matchingTuples;

	private List<RegisteredReaction> matchedReactions = new LinkedList<RegisteredReaction>();

	private ReactorOp op;

	private byte code;

	private ITuple newTuple;

	private RegisteredReaction newReaction;

	/**
	 * Constructor method.
	 * 
	 * @param ts
	 *            the (proxied) tuplespace
	 * @param registry
	 *            the hashtable of strong reactions: it is initialized and
	 *            managed by the LimeTSMgr, but since Reactor automatically
	 *            removes strong once reactions after firing, Reactor needs to
	 *            update it too
	 * @throws TupleSpaceEngineException
	 */
	OptimizedReactor(ITupleSpace ts, Hashtable registry) {
		// sets the tuplespace
		this.ts = ts;
		// sets the hashtable of strong reactions
		this.rrMgr = new TreeRRMgr(registry);
		logger.setLevel(Level.ALL);
	}

	public boolean[] remove(ReactionID[] rids) {
		return rrMgr.remove(rids);
	}

	/**
	 * Returns whether the reactor is running now. If the reactor is running
	 * then:
	 * <P>
	 * (1) it could be executing ("within" the listener of) a strong reaction <br>
	 * (2) it could be just performing the cycle-scan of reactions / tuples
	 * </P>
	 * 
	 * @return <code>boolean</code>
	 */
	public boolean isRunning() {
		return running;
	}

	private ITuple[] findTuples(ITuple t, boolean toDelete) {
		List<ITuple> matched = new ArrayList<ITuple>();
		ListIterator<ReactorOp> li = ops.listIterator();
		while (li.hasNext()) {
			ReactorOp op = li.next();
			if (op.code == LimeConstants.OUT) {
				ITuple tu = (ITuple) op.para;
				if (TupleSpaceProxy.matchTuples(t, tu)) {
					matched.add(tu);
					if (toDelete)
						li.remove();
				}
			}
		}
		return matched.toArray(new ITuple[0]);

	}

	private ITuple[] ingFromOPS(ITuple t) {
		return findTuples(t, true);
	}

	private ITuple[] rdgFromOPS(ITuple t) {
		return findTuples(t, false);
	}

	/**
	 * Reads a tuple (returns a deep copy, see <i>TupleSpaceProxy</i>).
	 * 
	 * @param t
	 *            template
	 * @return tuple found
	 */
	public ITuple doRead(ITuple t) {
		ITuple[] candidates = doReadg(t);
		if (candidates == null || candidates.length == 0)
			return null;
		else {
			logger.info("read: " + candidates[0]);
			return candidates[0];
		}
		// ITuple result = null;
		// try {
		// result = ts.rdp(t);
		// } catch (TupleSpaceException tse) {
		// throw new LimeRuntimeException("tuplespace engine error", tse);
		// }
		// // TODO if (result == null), need search in ops?
		// return result;
	}

	/**
	 * Reads a group of tuples (returns deep copies, see
	 * <i>TupleSpaceProxy</i>).
	 * 
	 * @param t
	 *            template
	 * @return tuples found
	 */
	ITuple[] doReadg(ITuple t) {
		ITuple[] result1 = null;
		try {
			result1 = ts.rdg(t);
		} catch (TupleSpaceException tse) {
			throw new LimeRuntimeException("tuplespace engine error", tse);
		}

		ITuple[] result2 = rdgFromOPS(t);

		ITuple[] ret = Arrayss.mergeArrays(new ITuple[][] { result1, result2 },
				ITuple.class);
		logger.info("rdg: " + Arrays.toString(ret));
		return ret;
	}

	/**
	 * Takes a tuple (= returns a deep copy - see <i>TupleSpaceProxy</i> - and
	 * removes the original tuple).
	 * 
	 * @param t
	 *            template
	 * @return tuple found
	 */
	public ITuple doTake(ITuple t) {
		ITuple[] candidates = doReadg(t);
		if (candidates == null || candidates.length == 0)
			return null;
		else {
			ITuple ret = doTakeg(candidates[0])[0];
			logger.info("take: " + ret);
			return ret;
		}
		// ITuple result = null;
		//
		// try {
		// result = ts.inp(t);
		// } catch (TupleSpaceException tse) {
		// throw new LimeRuntimeException("tuplespace engine error", tse);
		// }
		//
		// return result;
	}

	/**
	 * Takes a group of tuples (= returns deep copies - see
	 * <i>TupleSpaceProxy</i> - and removes the original tuples).
	 * 
	 * @param t
	 *            template
	 * @return tuples found
	 */
	public ITuple[] doTakeg(ITuple t) {
		ITuple[] result1 = null;
		try {
			result1 = ts.ing(t);
		} catch (TupleSpaceException tse) {
			throw new LimeRuntimeException("tuplespace engine error", tse);
		}

		ITuple[] result2 = ingFromOPS(t);

		ITuple[] ret = Arrayss.mergeArrays(new ITuple[][] { result1, result2 },
				ITuple.class);
		logger.info("ing: " + Arrays.toString(ret));
		invalidateMatching(t);
		return ret;
	}

	/**
	 * Those now are matching must be invalidated. Otherwise, tuples deleted
	 * from ts can still trigger reaction because they have been deep copied and
	 * referenced.
	 * 
	 * @param t
	 */
	private void invalidateMatching(ITuple t) {
		if (matchingTuples != null) {
			for (int i = 0; i < matchingTuples.length; i++) {
				if (TupleSpaceProxy.matchTuples(t, matchingTuples[i])) {
					deleteds[i] = true;
				}
			}
		}
	}

	/**
	 * Runs the reactive program ("runs the reactor").
	 * 
	 * @param wt
	 *            tuples to append to the workingTuples (they can be passed also
	 *            using the "writeWorkingTuples" method)
	 */
	void runReactiveProgram() {
		running = true;
		op = null;
		while (!ops.isEmpty()) {
			op = ops.remove(0);
			code = op.code;
			newTuple = null;
			newReaction = null;

			logger.info(op.toString());

			switch (code) {
			case LimeConstants.OUT:
			case LimeConstants.RD:
				newTuple = (ITuple) op.para;
				if (code == LimeConstants.OUT) {
					// move to the tuplespace
					try {
						ts.out(newTuple);
					} catch (TupleSpaceException tse) {
						throw new LimeRuntimeException(
								"tuplespace engine error", tse);
					}
				}
				rrMgr.findMatchTemplate(newTuple, matchedReactions);
				for (Iterator<RegisteredReaction> iterator = matchedReactions
						.iterator(); iterator.hasNext();) {
					RegisteredReaction r = iterator.next();
					ReactionID id = r.getReactionID();
					if (r.getTypeConstant() == code && rrMgr.containsKey(id)) {
						switch (r.getMode()) {
						case Reaction.ONCE:
							reactOnce(newTuple, r);
							break;
						case Reaction.ONCEPERTUPLE:
							react(newTuple, r);
							break;
						default:
							throw new RuntimeException("bad reaction mode:"
									+ r.getMode() + ".");
							// break;
						}
					}
				}
				matchedReactions.clear();
				break;
			case LimeConstants.INSTALL_STRONG:
				newReaction = (RegisteredReaction) op.para;
				rrMgr.put(newReaction);
				if (newReaction.getType() == ReactionType.Out) {
					ITuple template = newReaction.getTemplate();
					switch (newReaction.getMode()) {
					case Reaction.ONCE:
						ITuple t = null;
						try {
							t = ts.rdp(template);
						} catch (TupleSpaceException tse) {
							throw new LimeRuntimeException(
									"tuplespace engine error", tse);
						}
						if (t != null) {
							reactOnce(t, newReaction);
						}
						break;
					case Reaction.ONCEPERTUPLE:
						try {
							matchingTuples = ts.rdg(template);
						} catch (TupleSpaceException tse) {
							throw new LimeRuntimeException(
									"tuplespace engine error", tse);
						}
						if (matchingTuples != null) {
							deleteds = new boolean[matchingTuples.length];
							Arrays.fill(deleteds, false);
							ITuple tj = null;
							for (int j = 0; j < matchingTuples.length; j++) {
								tj = matchingTuples[j];
								if (!deleteds[j]
										&& !newReaction
												.alreadyReactedTo(LimeWrapper
														.getID(tj))) {
									react(tj, newReaction);
								}
							}
						}
						break;
					default:
						throw new RuntimeException("bad reaction mode:"
								+ newReaction.getMode() + ".");
						// break;
					}
				}
					break;
			case LimeConstants.INP:
				break;
			case LimeConstants.BYE:
				break;

			default:
				throw new RuntimeException("bad reactor operation code:" + code
						+ ".");
				// break;
			}
		}// end while (!ops.isEmpty())
		running = false;
	}

	// reacts to tuple "t" invoking the listener of the registered reaction "r":
	// please note that the passed
	// ReactionEvent wraps a deep copy of the triggering tuple
	private void react(ITuple t, RegisteredReaction r) {
		ReactionEvent e = new ReactionEvent(LimeTupleSpace.deepCopy(t));
		r.getListener().reactsToOut(e);
	}

	// fires the registered reaction using the specified tuple. Then removes the
	// registered reaction from this reactor
	private void reactOnce(ITuple t, RegisteredReaction r) {
		logger.info("react tuple " + t.toString() + "\t" + "reaction "
				+ r.toString());

		remove(new ReactionID[] { r.getReactionID() });
		react(t, r);
	}

	private void postOp(ReactorOp reactorOp) {
		ops.add(reactorOp);
	}

	void post(byte code, ITuple t) {
		if (t != null)
			postOp(new ReactorOp(code, t));
	}

	void post(byte code, ITuple[] t) {
		if (t != null) {
			for (int i = 0; i < t.length; i++) {
				postOp(new ReactorOp(code, t[i]));
			}
		}
	}

	void post(RegisteredReaction[] rr) {
		if (rr != null) {
			for (int i = 0; i < rr.length; i++) {
				postOp(new ReactorOp(LimeConstants.INSTALL_STRONG, rr[i]));
			}
		}
	}

}
